package org.spider.download.httpclient;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.JavaVersion;
import org.apache.commons.lang3.SystemUtils;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.*;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.spider.model.DownloadRequest;

import javax.net.ssl.*;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

/**
 * @author code4crafter@gmail.com <br>
 * @since 0.4.0
 */
@Slf4j
public class HttpClientGenerator {

    private static volatile PoolingHttpClientConnectionManager connectionManager;

    public HttpClientGenerator() {
        if (connectionManager == null) {
            Registry<ConnectionSocketFactory> reg = RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("http", PlainConnectionSocketFactory.INSTANCE)
                    .register("https", buildSSLConnectionSocketFactory())
                    .build();
            connectionManager = new PoolingHttpClientConnectionManager(reg);
            // 连接池最大连接数
            connectionManager.setMaxTotal(600);
            // 每个路由最大连接数
            connectionManager.setDefaultMaxPerRoute(300);
        }
    }

    private SSLConnectionSocketFactory buildSSLConnectionSocketFactory() {
        try {
            SSLContext sslContext = createIgnoreVerifySSL();
            String[] supportedProtocols;
            if (SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_11)) {
                supportedProtocols = new String[] { "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" };
            } else {
                supportedProtocols = new String[] { "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" };
            }
            return new SSLConnectionSocketFactory(sslContext, supportedProtocols,
                    null,
                    // 优先绕过安全证书
                    new DefaultHostnameVerifier());
        } catch (KeyManagementException | NoSuchAlgorithmException e) {
            log.error("ssl connection fail", e);
        }
        return SSLConnectionSocketFactory.getSocketFactory();
	}

    private SSLContext createIgnoreVerifySSL() throws NoSuchAlgorithmException, KeyManagementException {
        // 实现一个X509TrustManager接口，用于绕过验证，不用修改里面的方法
        X509TrustManager trustManager = new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { }
            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { }
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };
        SSLContext sc = SSLContext.getInstance("TLS");
        sc.init(null, new TrustManager[] { trustManager }, null);
        return sc;
	}

    public CloseableHttpClient getClient(DownloadRequest downloadRequest) {
        return generateClient(downloadRequest);
    }

    private CloseableHttpClient generateClient(DownloadRequest downloadRequest) {
        HttpClientBuilder httpClientBuilder = HttpClients.custom();
        httpClientBuilder.setConnectionManager(connectionManager);
        // 配置超时回调机制
        HttpRequestRetryHandler retryHandler = (exception, executionCount, httpContext) -> {
            // 如果已经重试了3次，就放弃
            if (executionCount >= 3) { return false; }
            // 如果服务器丢掉了连接，那么就重试
            if (exception instanceof NoHttpResponseException) { return true; }
            // 不要重试SSL握手异常
            if (exception instanceof SSLHandshakeException) { return false; }
            // 超时
            if (exception instanceof InterruptedIOException) { return true; }
            // 目标服务器不可达
            if (exception instanceof UnknownHostException) { return false; }
            // 连接被拒绝，一直为false，所以取消
//            if (exception instanceof ConnectTimeoutException) { return false; }
            // ssl握手异常
            if (exception instanceof SSLException) { return false; }
            HttpClientContext clientContext = HttpClientContext.adapt(httpContext);
            HttpRequest httpRequest = clientContext.getRequest();
            // 如果请求是幂等的，就再次尝试
            return !(httpRequest instanceof HttpEntityEnclosingRequest);
        };
        httpClientBuilder.setRetryHandler(retryHandler);
        // 解决post/redirect/post 302跳转问题
        httpClientBuilder.setRedirectStrategy(new CustomRedirectStrategy());
        SocketConfig.Builder socketConfigBuilder = SocketConfig.custom();
        socketConfigBuilder.setSoKeepAlive(true).setTcpNoDelay(true);
        socketConfigBuilder.setSoTimeout(downloadRequest.getSocketTimeout());
        SocketConfig socketConfig = socketConfigBuilder.build();
        httpClientBuilder.setDefaultSocketConfig(socketConfig);
        connectionManager.setDefaultSocketConfig(socketConfig);
        return httpClientBuilder.build();
    }
}
